package dao

import (
	"context"
	"fmt"
	"net/url"
	"sort"
	"strings"
	"time"

	v1 "open/api/open/admin/v1"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-redis/redis/v8"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"open/internal/model"
	"open/internal/types"
	"open/pkg/alipay"
)

const alipayAuthTokenKey = "open/alipay/auth_token/%s"
const alipayAesKey = "open/alipay/aes_key/%s"

// GetAlipayAseKey 获取或者创建AESKey 缓存到redis
func (d *Dao) GetAlipayAseKey(ctx context.Context, appID string) (string, error) {
	key := fmt.Sprintf(alipayAesKey, appID)
	aesKey, err := d.cache.Get(ctx, key).Result()
	if err != nil {
		if err == redis.Nil {
			resp, err := d.alipay.App.GetAesKey(ctx,
				&alipay.GetAesKeyBiz{MerchantAppID: appID},
				alipay.EncryptType("AES"))
			if err != nil {
				if alipayError, ok := err.(*alipay.ErrorResponse); ok {
					if alipayError.Code == "40004" {
						resp, err := d.alipay.App.SetAesKey(ctx,
							&alipay.SetAesKeyBiz{MerchantAppID: appID},
							alipay.EncryptType("AES"))
						if err != nil {
							return "", err
						}
						aesKey = resp.AesKey
					}
				}
				if aesKey == "" {
					return "", err
				}
			} else {
				aesKey = resp.AesKey
			}
			if aesKey != "" {
				if err := d.cache.Set(ctx, key, aesKey, 48*time.Hour).Err(); err != nil {
					return "", err
				}
				return aesKey, nil
			}
		} else {
			return "", err
		}
	}
	return aesKey, nil
}

// GetAlipayAuthToken 获取支付宝授权token
func (d *Dao) GetAlipayAuthToken(ctx context.Context, appID string) (string, error) {
	key := fmt.Sprintf(alipayAuthTokenKey, appID)
	token, err := d.cache.Get(ctx, key).Result()
	if err != nil {
		if err == redis.Nil {
			subApp, err := d.GetAlipaySubApp(ctx, appID)
			if err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					err = errors.BadRequest(
						v1.ErrorCode_AUTH_UNAUTHORIZED.String(),
						"应用未授权或者商家主动取消授权,请重新授权后重试！")
				}
				return "", err
			}
			if err := d.cache.Set(ctx, key, subApp.AppAuthToken, 48*time.Hour).Err(); err != nil {
				return "", err
			}
			return subApp.AppAuthToken, nil
		} else {
			return "", err
		}
	}
	return token, nil
}

// GetAlipayMiniApp 获取支付宝小程序信息
func (d *Dao) GetAlipayMiniApp(ctx context.Context, appID string) (app *model.AlipayMiniApp, err error) {
	app = new(model.AlipayMiniApp)
	err = d.FromContext(ctx).Where("app_id = ?", appID).First(app).Error
	return
}

// GetAlipayMiniTesters 获取支付宝小程序体验者
func (d Dao) GetAlipayMiniTesters(ctx context.Context, appID string) ([]*alipay.AppMemberInfo, error) {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	appMembersResp, err := d.alipay.App.QueryAppMembers(ctx, &alipay.QueryAppMembersBiz{
		Role: "EXPERIENCER",
	}, alipay.AppAuthToken(token))
	if err != nil {
		return nil, err
	}
	return appMembersResp.AppMemberInfoList, nil
}

// SaveAlipayMiniApp 保存支付宝小程序信息
func (d *Dao) SaveAlipayMiniApp(ctx context.Context, alipayMiniApp *model.AlipayMiniApp) error {
	return d.FromContext(ctx).Save(alipayMiniApp).Error
}

// AddAlipayMiniTester 添加支付宝小程序体验者
func (d *Dao) AddAlipayMiniTester(ctx context.Context, appID string, logonID string) error {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	return d.alipay.App.CreateMember(ctx, &alipay.CreateAppMemberBiz{
		LogonID: logonID,
		Role:    "EXPERIENCER",
	}, alipay.AppAuthToken(token))
}

// DeleteAlipayMiniTester 删除支付宝小程序体验者
func (d *Dao) DeleteAlipayMiniTester(ctx context.Context, appID string, userID string) error {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	return d.alipay.App.DeleteMember(ctx, &alipay.DeleteMemberBiz{
		UserID: userID,
		Role:   "EXPERIENCER",
	}, alipay.AppAuthToken(token))
}

// UpdateAlipayQRCode 更新支付宝二维码
func (d *Dao) UpdateAlipayQRCode(ctx context.Context, appID string) error {
	alipayMiniApp, err := d.GetAlipayMiniApp(ctx, appID)
	if err != nil {
		return err
	}
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	biz := alipay.CreateAppQRCodeBiz{
		URLParam:   "pages/index/index",
		QueryParam: "scene=public",
		Describe:   alipayMiniApp.Name + "二维码",
	}
	var resp *alipay.CreateAppQRCodeResp
	resp, err = d.alipay.App.CreateAppQRCode(context.Background(), &biz, alipay.AppAuthToken(token))
	if err != nil {
		return err
	}
	alipayMiniApp.QrCodeURL = resp.QRCodeURL
	err = d.SaveAlipayMiniApp(ctx, alipayMiniApp)
	if err != nil {
		return err
	}
	return err
}

// GetLatestAlipayTemplate 获取最新的支付宝模版
func (d *Dao) GetLatestAlipayTemplate(ctx context.Context) (template *model.AlipayTemplate, err error) {
	template = new(model.AlipayTemplate)
	err = d.FromContext(ctx).Order("created_at desc").First(template).Error
	return
}

// CreateAlipayMiniExperience 创建支付宝小程序体验版
func (d *Dao) CreateAlipayMiniExperience(ctx context.Context, appID string, biz *alipay.CreateExperienceBiz) error {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	return d.alipay.Mini.CreateExperience(ctx,
		&alipay.CreateExperienceBiz{AppVersion: biz.AppVersion, BundleID: biz.BundleID},
		alipay.AppAuthToken(token))
}

// CommitAlipayMini 提交支付宝小程序代码
func (d *Dao) CommitAlipayMini(ctx context.Context, appID string, biz *alipay.UploadVersionBiz) error {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	return d.alipay.Mini.UploadVersion(ctx, biz, alipay.AppAuthToken(token))
}

// CommitAuditAlipayMini 支付宝小程序提交审核
func (d *Dao) CommitAuditAlipayMini(ctx context.Context, appID string, biz alipay.ApplyVersionAuditBiz) error {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	return d.alipay.Mini.ApplyVersionAudit(ctx, &biz, alipay.AppAuthToken(token))
}

// ReleaseAlipayMini 发布支付宝小程序
func (d *Dao) ReleaseAlipayMini(ctx context.Context, appID string, biz alipay.OnlineVersionBiz) error {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	return d.alipay.Mini.OnlineVersion(ctx, &biz, alipay.AppAuthToken(token))
}

// GetAlipayVersionDetail 查询版本详情
func (d *Dao) GetAlipayVersionDetail(ctx context.Context, appID string, biz alipay.QueryVersionDetailBiz) (*alipay.VersionDetail, error) {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	return d.alipay.Mini.QueryVersionDetail(ctx, &biz, alipay.AppAuthToken(token))
}

// CancelAlipayVersionAudited 取消审核
func (d *Dao) CancelAlipayVersionAudited(ctx context.Context, appID string, biz alipay.CancelVersionAuditedBiz) error {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return err
	}
	return d.alipay.Mini.CancelVersionAudited(ctx, &biz, alipay.AppAuthToken(token))
}

// CreateAlipayOpenSubApp 创建支付宝子应用
func (d *Dao) CreateAlipayOpenSubApp(ctx context.Context, app *model.AlipayOpenSubApp) error {
	err := d.FromContext(ctx).Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "app_id"}, {Name: "open_app_id"}},
		UpdateAll: true,
	}).Create(app).Error
	if err != nil {
		return err
	}
	key := fmt.Sprintf(alipayAuthTokenKey, app.AppID)
	if err = d.cache.Del(ctx, key).Err(); err != nil {
		return err
	}
	return nil
}

// VerifyAlipaySign 签名校验
func (d *Dao) VerifyAlipaySign(values url.Values, sign string) error {
	var buf strings.Builder
	keys := make([]string, 0, len(values))
	for k := range values {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		if k == "sign" {
			continue
		}
		if k == "sign_type" {
			continue
		}
		vs := values[k]
		for _, v := range vs {
			if v == "" {
				continue
			}
			if buf.Len() > 0 {
				buf.WriteByte('&')
			}
			buf.WriteString(k)
			buf.WriteByte('=')
			buf.WriteString(v)
		}
	}
	valuesStr := buf.String()
	return d.alipay.VerifySign([]byte(valuesStr), sign)
}

// AlipaySuccessWithSign 签名校验
func (d *Dao) AlipaySuccessWithSign() string {
	resp, _ := d.alipay.SuccessWithSign()
	return resp
}

// LockAlipaySubApp 锁定支付宝子应用
func (d *Dao) LockAlipaySubApp(ctx context.Context, appID string) (*model.AlipayOpenSubApp, error) {
	app := new(model.AlipayOpenSubApp)
	err := d.FromContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("app_id = ?", appID).First(app).Error
	return app, err
}

// GetAlipaySubApp 获取支付宝子应用
func (d *Dao) GetAlipaySubApp(ctx context.Context, appID string) (*model.AlipayOpenSubApp, error) {
	app := new(model.AlipayOpenSubApp)
	err := d.FromContext(ctx).Where("app_id = ? and status = ?", appID, types.AlipayAuthorized).First(app).Error
	return app, err
}

// SaveAlipaySubApp 保存支付宝子应用
func (d *Dao) SaveAlipaySubApp(ctx context.Context, app *model.AlipayOpenSubApp) error {
	err := d.FromContext(ctx).Save(app).Error
	if err != nil {
		return err
	}
	key := fmt.Sprintf(alipayAuthTokenKey, app.AppID)
	if err = d.cache.Del(ctx, key).Err(); err != nil {
		return err
	}
	return nil
}

// GetAlipayMiniTestQRCode 获取支付宝小程序体验码
func (d *Dao) GetAlipayMiniTestQRCode(ctx context.Context, appID string, version string) (*alipay.ExperienceStatus, error) {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	es, err := d.alipay.Mini.QueryExperience(ctx, &alipay.QueryExperienceBiz{
		AppVersion: version,
		BundleID:   "com.alipay.alipaywallet",
	}, alipay.AppAuthToken(token))
	if err != nil {
		return nil, err
	}
	return es, nil
}

// CreateAlipayMiniQRCode 创建支付宝小程序二维码
func (d *Dao) CreateAlipayMiniQRCode(ctx context.Context, appID string, req *v1.CreateAlipayMiniQRCodeRequest) (*alipay.CreateAppQRCodeResp, error) {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	resp, err := d.alipay.App.CreateAppQRCode(ctx, &alipay.CreateAppQRCodeBiz{
		URLParam:   req.UrlParam,
		QueryParam: req.QueryParam,
		Describe:   req.Describe,
	}, alipay.AppAuthToken(token))
	if err != nil {
		return nil, err
	}
	return resp, nil
}

// AuthorizeAlipay 授权支付宝应用
func (d *Dao) AuthorizeAlipay(ctx context.Context, appAuthCode string) (string, *alipay.AuthTokenResp, error) {
	resp, err := d.alipay.App.AuthToken(ctx, &alipay.AuthTokenBiz{
		GrantType: "authorization_code",
		Code:      appAuthCode,
	})
	if err != nil {
		return "", nil, err
	}
	return d.conf.Open.Alipay.AppId, resp, err
}

// GetAlipayMiniBaseInfo 获取小程序基础信息
func (d *Dao) GetAlipayMiniBaseInfo(ctx context.Context, appID string) (*alipay.BaseInfo, error) {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	info, err := d.alipay.Mini.QueryBaseInfo(ctx, alipay.AppAuthToken(token))
	if err != nil {
		return nil, err
	}
	return info, err
}

// GetOrCreateAlipayMiniApp 获取或创建支付宝小程序应用信息
func (d *Dao) GetOrCreateAlipayMiniApp(ctx context.Context, appID string) (app *model.AlipayMiniApp, err error) {
	app = new(model.AlipayMiniApp)
	err = d.FromContext(ctx).Where("app_id = ?", appID).FirstOrCreate(app).Error
	return
}

// OauthAlipayUser 授权支付宝用户
func (d *Dao) OauthAlipayUser(ctx context.Context, appID string, code string) (*alipay.AuthTokenResp, error) {
	token, err := d.GetAlipayAuthToken(ctx, appID)
	if err != nil {
		return nil, err
	}
	resp, err := d.alipay.App.AuthToken(ctx, &alipay.AuthTokenBiz{
		GrantType: "authorization_code",
		Code:      code,
	}, alipay.AppAuthToken(token))
	if err != nil {
		return nil, err
	}
	return resp, err
}
